// Usage example:
// Messenger<float>.AddListener("myEvent", MyEventHandler);
// ...
// Messenger<float>.Broadcast("myEvent", 1.0f);
/*
	Writing an event listener
    void OnSpeedChanged(float speed)
    {
        this.speed = speed;
    }
    Registering an event listener
    void OnEnable()
    {
        Messenger<float>.AddListener("speed changed", OnSpeedChanged);
    }
    Unregistering an event listener
    void OnDisable()
    {
        Messenger<float>.RemoveListener("speed changed", OnSpeedChanged);
    }
	Broadcasting an event
    if (speed != lastSpeed)
    {
        Messenger<float>.Broadcast("speed changed", speed);
    }
 */

using System;
using System.Collections.Generic;
using Messegers;
using System.Linq;
using UnityEngine;

public enum MessengerMode
{
    DontRequireListener,
    RequireListener
}

internal static class MessengerInternal
{
    public const MessengerMode DefaultMode = MessengerMode.DontRequireListener;
    public static readonly Dictionary<Hash128, Delegate> EventTable = new Dictionary<Hash128, Delegate>();

    public  static Hash128 GetHashCodeByString(string str)
    {
        return Hash128.Compute(str);
    }

    public static bool OnListenerAdding(string eventType, Delegate listenerBeingAdded,out Hash128 hashCode)
    {
        hashCode = GetHashCodeByString(eventType);
        if (!EventTable.ContainsKey(hashCode)) EventTable.Add(hashCode, null);

        var d = EventTable[hashCode];

        if (d != null && d.GetType() != listenerBeingAdded.GetType())
        {
            throw new ListenerException(string.Format(
                "Attempting to add listener with inconsistent signature for event type {0}. Current listeners have type {1} and listener being added has type {2}",
                eventType, d.GetType().Name, listenerBeingAdded.GetType().Name));
        }

        if (d != null && d.GetInvocationList().Contains(listenerBeingAdded))
        {
            Debug.LogErrorFormat("Attempting to add listener with exist callback {1} for event type {0}", eventType,
                listenerBeingAdded.GetType().Name);
            return false;
        }

        return true;
    }

    public static bool OnListenerRemoving(string eventType, Delegate listenerBeingRemoved,out Hash128 hashCode)
    {
        hashCode = GetHashCodeByString(eventType);
        if (EventTable.TryGetValue(hashCode, out var d))
        {
            if (d == null)
            {
                Debug.LogErrorFormat(
                    "Attempting to remove listener with for event type {0} but current listener is null.", eventType);
                // throw new ListenerException(string.Format(
                //     "Attempting to remove listener with for event type {0} but current listener is null.", eventType));
                return false;
            }

            if (d.GetType() != listenerBeingRemoved.GetType())
            {
                Debug.LogErrorFormat(
                    "Attempting to remove listener with inconsistent signature for event type {0}. Current listeners have type {1} and listener being removed has type {2}",
                    eventType, d.GetType().Name, listenerBeingRemoved.GetType().Name);
                //throw new ListenerException(string.Format(
                //    "Attempting to remove listener with inconsistent signature for event type {0}. Current listeners have type {1} and listener being removed has type {2}",
                //    eventType, d.GetType().Name, listenerBeingRemoved.GetType().Name));
                return false;
            }

            return true;
        }
        else
        {
            Debug.LogErrorFormat(
                "Attempting to remove listener for type {0} but Messenger doesn't know about this event type.",
                eventType);
            // throw new ListenerException(string.Format(
            //     "Attempting to remove listener for type {0} but Messenger doesn't know about this event type.",
            //     eventType));
            return false;
        }
    }

    public static void OnListenerRemoved(Hash128 hashCode)
    {
        if (EventTable.ContainsKey(hashCode))
        {
            if (EventTable[hashCode] == null) EventTable.Remove(hashCode);
        }
    }

    public static void OnBroadcasting(string eventType, MessengerMode mode,out Hash128 hashCode)
    {
        hashCode = GetHashCodeByString(eventType);
        if (mode == MessengerMode.RequireListener && !EventTable.ContainsKey(hashCode))
            throw new BroadcastException(string.Format("Broadcasting message {0} but no listener found.", eventType));
    }

    public static BroadcastException CreateBroadcastSignatureException(string eventType)
    {
        return new BroadcastException(string.Format(
            "Broadcasting message {0} but listeners have a different signature than the broadcaster.", eventType));
    }

    public class BroadcastException : Exception
    {
        public BroadcastException(string msg) : base(msg)
        {
        }
    }

    private class ListenerException : Exception
    {
        public ListenerException(string msg) : base(msg)
        {
        }
    }
}

public static class MessengerHelper
{
    public static Dictionary<Hash128, Delegate> GetAllEvent()
    {
        return MessengerInternal.EventTable;
    }

    public static Dictionary<Hash128, Delegate>.KeyCollection GetAllEventKeys()
    {
        return MessengerInternal.EventTable.Keys;
    }
}

// No parameters
public static class Messenger
{
    public static void AddListener(string eventType, Callback handler)
    {
        if (MessengerInternal.OnListenerAdding(eventType, handler,out var hashCode))
        {
            MessengerInternal.EventTable[hashCode] = (Callback)MessengerInternal.EventTable[hashCode] + handler;
        }
    }

    public static void RemoveListener(string eventType, Callback handler)
    {
        if (MessengerInternal.OnListenerRemoving(eventType, handler,out var hashCode))
        {
            try
            {
                MessengerInternal.EventTable[hashCode] = (Callback)MessengerInternal.EventTable[hashCode] - handler;
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }
        }

        MessengerInternal.OnListenerRemoved(hashCode);
    }

    public static void ClearListener(string eventType)
    {
        var hashCode = MessengerInternal.GetHashCodeByString(eventType);
        if (MessengerInternal.EventTable.ContainsKey(hashCode)) MessengerInternal.EventTable.Remove(hashCode);
    }

    public static void Broadcast(string eventType, MessengerMode mode = MessengerInternal.DefaultMode)
    {
        MessengerInternal.OnBroadcasting(eventType, mode,out var hashCode);
        if (!MessengerInternal.EventTable.TryGetValue(hashCode, out var d)) return;
        if (d is Callback callback)
            callback();
        else
            throw MessengerInternal.CreateBroadcastSignatureException(eventType);
    }
}

// One parameter
public static class Messenger<T>
{
    public static void AddListener(string eventType, Callback<T> handler)
    {
        if (MessengerInternal.OnListenerAdding(eventType, handler,out var hashCode))
        {
            MessengerInternal.EventTable[hashCode] = (Callback<T>)MessengerInternal.EventTable[hashCode] + handler;
        }
    }

    public static void RemoveListener(string eventType, Callback<T> handler)
    {
        if (MessengerInternal.OnListenerRemoving(eventType, handler,out var hashCode))
        {
            try
            {
                MessengerInternal.EventTable[hashCode] =
                    (Callback<T>)MessengerInternal.EventTable[hashCode] - handler;
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }
        }

        MessengerInternal.OnListenerRemoved(hashCode);
    }

    public static void Broadcast(string eventType, T arg1, MessengerMode mode = MessengerInternal.DefaultMode)
    {
        MessengerInternal.OnBroadcasting(eventType, mode,out var hashCode);
        if (!MessengerInternal.EventTable.TryGetValue(hashCode, out var d)) return;
        if (d is Callback<T> callback)
            callback(arg1);
        else
            throw MessengerInternal.CreateBroadcastSignatureException(eventType);
    }
}


// Two parameters
public static class Messenger<T, U>
{
    public static void AddListener(string eventType, Callback<T, U> handler)
    {
        if (MessengerInternal.OnListenerAdding(eventType, handler,out var hashCode))
        {
            MessengerInternal.EventTable[hashCode] = (Callback<T, U>)MessengerInternal.EventTable[hashCode] + handler;
        }
    }

    public static void RemoveListener(string eventType, Callback<T, U> handler)
    {
        if (MessengerInternal.OnListenerRemoving(eventType, handler,out var hashCode))
        {
            try
            {
                MessengerInternal.EventTable[hashCode] =
                    (Callback<T, U>)MessengerInternal.EventTable[hashCode] - handler;
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }
        }

        MessengerInternal.OnListenerRemoved(hashCode);
    }

    public static void Broadcast(string eventType, T arg1, U arg2, MessengerMode mode = MessengerInternal.DefaultMode)
    {
        MessengerInternal.OnBroadcasting(eventType, mode,out var hashCode);
        if (!MessengerInternal.EventTable.TryGetValue(hashCode, out var d)) return;
        if (d is Callback<T, U> callback)
            callback(arg1, arg2);
        else
            throw MessengerInternal.CreateBroadcastSignatureException(eventType);
    }
}


// Three parameters
public static class Messenger<T, U, V>
{
    public static void AddListener(string eventType, Callback<T, U, V> handler)
    {
        if (MessengerInternal.OnListenerAdding(eventType, handler,out var hashCode))
        {
            MessengerInternal.EventTable[hashCode] =
                (Callback<T, U, V>)MessengerInternal.EventTable[hashCode] + handler;
        }
    }

    public static void RemoveListener(string eventType, Callback<T, U, V> handler)
    {
        if (MessengerInternal.OnListenerRemoving(eventType, handler,out var hashCode))
        {
            try
            {
                MessengerInternal.EventTable[hashCode] =
                    (Callback<T, U, V>)MessengerInternal.EventTable[hashCode] - handler;
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }
        }

        MessengerInternal.OnListenerRemoved(hashCode);
    }

    public static void Broadcast(string eventType, T arg1, U arg2, V arg3,
        MessengerMode mode = MessengerInternal.DefaultMode)
    {
        MessengerInternal.OnBroadcasting(eventType, mode,out var hashCode);
        if (!MessengerInternal.EventTable.TryGetValue(hashCode, out var d)) return;
        if (d is Callback<T, U, V> callback)
            callback(arg1, arg2, arg3);
        else
            throw MessengerInternal.CreateBroadcastSignatureException(eventType);
    }
}

public static class Messenger<T, U, V, K>
{
    public static void AddListener(string eventType, Callback<T, U, V, K> handler)
    {
        if (MessengerInternal.OnListenerAdding(eventType, handler,out var hashCode))
        {
            MessengerInternal.EventTable[hashCode] =
                (Callback<T, U, V, K>)MessengerInternal.EventTable[hashCode] + handler;
        }
    }

    public static void RemoveListener(string eventType, Callback<T, U, V, K> handler)
    {
        if (MessengerInternal.OnListenerRemoving(eventType, handler,out var hashCode))
        {
            try
            {
                MessengerInternal.EventTable[hashCode] =
                    (Callback<T, U, V, K>)MessengerInternal.EventTable[hashCode] - handler;
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }
        }

        MessengerInternal.OnListenerRemoved(hashCode);
    }

    public static void Broadcast(string eventType, T arg1, U arg2, V arg3, K arg4,
        MessengerMode mode = MessengerInternal.DefaultMode)
    {
        MessengerInternal.OnBroadcasting(eventType, mode,out var hashCode);
        if (!MessengerInternal.EventTable.TryGetValue(hashCode, out var d)) return;
        if (d is Callback<T, U, V, K> callback)
            callback(arg1, arg2, arg3, arg4);
        else
            throw MessengerInternal.CreateBroadcastSignatureException(eventType);
    }
}

public static class Messenger<T, U, V, K, Z>
{
    public static void AddListener(string eventType, Callback<T, U, V, K, Z> handler)
    {
        if (MessengerInternal.OnListenerAdding(eventType, handler,out var hashCode))
        {
            MessengerInternal.EventTable[hashCode] =
                (Callback<T, U, V, K, Z>)MessengerInternal.EventTable[hashCode] + handler;
        }
    }

    public static void RemoveListener(string eventType, Callback<T, U, V, K, Z> handler)
    {
        if (MessengerInternal.OnListenerRemoving(eventType, handler,out var hashCode))
        {
            try
            {
                MessengerInternal.EventTable[hashCode] =
                    (Callback<T, U, V, K, Z>)MessengerInternal.EventTable[hashCode] - handler;
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }
        }

        MessengerInternal.OnListenerRemoved(hashCode);
    }

    public static void Broadcast(string eventType, T arg1, U arg2, V arg3, K arg4, Z arg5,
        MessengerMode mode = MessengerInternal.DefaultMode)
    {
        MessengerInternal.OnBroadcasting(eventType, mode,out var hashCode);
        if (!MessengerInternal.EventTable.TryGetValue(hashCode, out var d)) return;
        if (d is Callback<T, U, V, K, Z> callback)
            callback(arg1, arg2, arg3, arg4, arg5);
        else
            throw MessengerInternal.CreateBroadcastSignatureException(eventType);
    }
}

public static class Messenger<T, U, V, K, Z, X>
{
    public static void AddListener(string eventType, Callback<T, U, V, K, Z, X> handler)
    {
        if (MessengerInternal.OnListenerAdding(eventType, handler,out var hashCode))
        {
            MessengerInternal.EventTable[hashCode] =
                (Callback<T, U, V, K, Z, X>)MessengerInternal.EventTable[hashCode] + handler;
        }
    }

    public static void RemoveListener(string eventType, Callback<T, U, V, K, Z, X> handler)
    {
        if (MessengerInternal.OnListenerRemoving(eventType, handler,out var hashCode))
        {
            try
            {
                MessengerInternal.EventTable[hashCode] =
                    (Callback<T, U, V, K, Z, X>)MessengerInternal.EventTable[hashCode] - handler;
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }
        }

        MessengerInternal.OnListenerRemoved(hashCode);
    }

    public static void Broadcast(string eventType, T arg1, U arg2, V arg3, K arg4, Z arg5, X arg6,
        MessengerMode mode = MessengerInternal.DefaultMode)
    {
        MessengerInternal.OnBroadcasting(eventType, mode,out var hashCode);
        if (!MessengerInternal.EventTable.TryGetValue(hashCode, out var d)) return;
        if (d is Callback<T, U, V, K, Z, X> callback)
            callback(arg1, arg2, arg3, arg4, arg5, arg6);
        else
            throw MessengerInternal.CreateBroadcastSignatureException(eventType);
    }
}

public static class Messenger<T, U, V, K, Z, X, Y>
{
    public static void AddListener(string eventType, Callback<T, U, V, K, Z, X, Y> handler)
    {
        if (MessengerInternal.OnListenerAdding(eventType, handler, out var hashCode))
        {
            MessengerInternal.EventTable[hashCode] =
                (Callback<T, U, V, K, Z, X, Y>)MessengerInternal.EventTable[hashCode] + handler;
        }
    }

    public static void RemoveListener(string eventType, Callback<T, U, V, K, Z, X, Y> handler)
    {
        if (MessengerInternal.OnListenerRemoving(eventType, handler, out var hashCode))
        {
            try
            {
                MessengerInternal.EventTable[hashCode] =
                    (Callback<T, U, V, K, Z, X, Y>)MessengerInternal.EventTable[hashCode] - handler;
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }
        }

        MessengerInternal.OnListenerRemoved(hashCode);
    }

    public static void Broadcast(string eventType, T arg1, U arg2, V arg3, K arg4, Z arg5, X arg6, Y arg7,
        MessengerMode mode = MessengerInternal.DefaultMode)
    {
        MessengerInternal.OnBroadcasting(eventType, mode, out var hashCode);
        if (!MessengerInternal.EventTable.TryGetValue(hashCode, out var d)) return;
        if (d is Callback<T, U, V, K, Z, X, Y> callback)
            callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7);
        else
            throw MessengerInternal.CreateBroadcastSignatureException(eventType);
    }
}